0 KM/H
← → Arrow keys or swipe
to switch lanes
NEON RACER
Dodge the oncoming traffic.
Don't crash.
Survive the night highway.
CRASHED!
Score: 0
High Score: 0
const carImg = new Image();
carImg.src = 'your-car-image-url.png'; // Replace with your image path
let carLoaded = false;
carImg.onload = () => { carLoaded = true; };
// Game state
let gameRunning = false;
let score = 0;
let highScore = localStorage.getItem('neonRacerHigh') || 0;
let speed = 0;
let maxSpeed = 15;
let frame = 0;
// 3 Lanes (x positions)
const lanes = [70, 200, 330];
let currentLane = 1;
// Player car
const player = {
x: lanes[1],
y: 550,
width: 50,
height: 80,
targetX: lanes[1],
tilt: 0
};
// Enemy cars
let enemies = [];
let roadOffset = 0;
// Custom car image
let customCarImage = null;
let imageLoaded = false;
// Audio context
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
// Engine sound (low rumble)
let engineOscillator = null;
function startEngineSound() {
if (engineOscillator) return;
engineOscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
engineOscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
engineOscillator.type = 'sawtooth';
engineOscillator.frequency.setValueAtTime(80, audioContext.currentTime);
gainNode.gain.setValueAtTime(0.05, audioContext.currentTime);
engineOscillator.start();
}
function updateEngineSound() {
if (!engineOscillator || !gameRunning) return;
const targetFreq = 60 + (speed * 10);
engineOscillator.frequency.setTargetAtTime(targetFreq, audioContext.currentTime, 0.1);
}
function stopEngineSound() {
if (engineOscillator) {
engineOscillator.stop();
engineOscillator = null;
}
}
// Crash sound
function playCrashSound() {
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.connect(gain);
gain.connect(audioContext.destination);
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(200, audioContext.currentTime);
osc.frequency.exponentialRampToValueAtTime(20, audioContext.currentTime + 0.5);
gain.gain.setValueAtTime(0.5, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
osc.start();
osc.stop(audioContext.currentTime + 0.5);
// Stop engine
stopEngineSound();
}
// Lane change sound
function playWhoosh() {
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.connect(gain);
gain.connect(audioContext.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(400, audioContext.currentTime);
osc.frequency.exponentialRampToValueAtTime(200, audioContext.currentTime + 0.1);
gain.gain.setValueAtTime(0.1, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);
osc.start();
osc.stop(audioContext.currentTime + 0.1);
}
// Handle image upload
document.getElementById('carImage').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(event) {
customCarImage = new Image();
customCarImage.onload = function() {
imageLoaded = true;
};
customCarImage.src = event.target.result;
};
reader.readAsDataURL(file);
}
});
// Input handling
document.addEventListener('keydown', (e) => {
if (!gameRunning) return;
if (e.key === 'ArrowLeft' && currentLane > 0) {
currentLane--;
player.targetX = lanes[currentLane];
player.tilt = -0.3;
playWhoosh();
}
if (e.key === 'ArrowRight' && currentLane < 2) {
currentLane++;
player.targetX = lanes[currentLane];
player.tilt = 0.3;
playWhoosh();
}
});
// Touch controls
let touchStartX = 0;
canvas.addEventListener('touchstart', (e) => {
touchStartX = e.touches[0].clientX;
});
canvas.addEventListener('touchend', (e) => {
if (!gameRunning) return;
const touchEndX = e.changedTouches[0].clientX;
const diff = touchStartX - touchEndX;
if (Math.abs(diff) > 30) {
if (diff > 0 && currentLane > 0) {
currentLane--;
player.targetX = lanes[currentLane];
player.tilt = -0.3;
playWhoosh();
} else if (diff < 0 && currentLane < 2) {
currentLane++;
player.targetX = lanes[currentLane];
player.tilt = 0.3;
playWhoosh();
}
}
});
function startGame() {
if (audioContext.state === 'suspended') {
audioContext.resume();
}
startEngineSound();
document.getElementById('startScreen').style.display = 'none';
document.getElementById('highScore').textContent = highScore;
gameRunning = true;
resetVariables();
gameLoop();
}
function resetVariables() {
score = 0;
speed = 0;
currentLane = 1;
player.x = lanes[1];
player.targetX = lanes[1];
player.tilt = 0;
enemies = [];
frame = 0;
}
function spawnEnemy() {
const lane = Math.floor(Math.random() * 3);
const types = ['sedan', 'truck', 'sport'];
const type = types[Math.floor(Math.random() * types.length)];
const enemy = {
x: lanes[lane],
y: -150,
lane: lane,
width: type === 'truck' ? 60 : 50,
height: type === 'truck' ? 100 : (type === 'sport' ? 70 : 80),
type: type,
speed: 3 + Math.random() * 2 + (speed * 0.1),
color: type === 'sedan' ? '#ff006e' : (type === 'truck' ? '#fb5607' : '#00d9ff')
};
// Don't spawn on top of another enemy
const tooClose = enemies.some(e => e.lane === lane && e.y < 100);
if (!tooClose) {
enemies.push(enemy);
}
}
function update() {
frame++;
// Accelerate
if (speed < maxSpeed) {
speed += 0.02;
}
// Update score
score += Math.floor(speed);
document.getElementById('score').textContent = Math.floor(score);
document.getElementById('speed').textContent = Math.floor(speed * 20);
// Update engine sound
updateEngineSound();
// Smooth lane change
player.x += (player.targetX - player.x) * 0.15;
// Return tilt to center
player.tilt *= 0.9;
// Road scrolling
roadOffset += speed;
if (roadOffset > 80) roadOffset = 0;
// Spawn enemies
const spawnRate = Math.max(30, 100 - Math.floor(speed * 3));
if (frame % spawnRate === 0) {
spawnEnemy();
}
// Update enemies
for (let i = enemies.length - 1; i >= 0; i--) {
let enemy = enemies[i];
enemy.y += enemy.speed + speed;
// Collision detection (pixel perfect-ish)
if (Math.abs(player.x - enemy.x) < 35 &&
Math.abs(player.y - enemy.y) < (player.height + enemy.height) / 2 - 10) {
playCrashSound();
gameOver();
return;
}
// Remove if off screen
if (enemy.y > canvas.height + 100) {
enemies.splice(i, 1);
score += 100; // Bonus for passing
}
}
}
function draw() {
// Clear
ctx.fillStyle = '#0a0a0e';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw road
ctx.fillStyle = '#151520';
ctx.fillRect(20, 0, 360, canvas.height);
// Road borders (neon)
ctx.shadowBlur = 20;
ctx.shadowColor = '#00d9ff';
ctx.strokeStyle = '#00d9ff';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(20, 0);
ctx.lineTo(20, canvas.height);
ctx.moveTo(380, 0);
ctx.lineTo(380, canvas.height);
ctx.stroke();
ctx.shadowBlur = 0;
// Lane markers (moving)
ctx.strokeStyle = 'rgba(255,255,255,0.3)';
ctx.lineWidth = 2;
ctx.setLineDash([40, 40]);
ctx.lineDashOffset = -roadOffset;
for (let i = 1; i < 3; i++) {
ctx.beginPath();
ctx.moveTo(20 + (360/3) * i, 0);
ctx.lineTo(20 + (360/3) * i, canvas.height);
ctx.stroke();
}
ctx.setLineDash([]);
// Draw enemies
for (let enemy of enemies) {
ctx.save();
ctx.translate(enemy.x, enemy.y);
// Shadow
ctx.fillStyle = 'rgba(0,0,0,0.5)';
ctx.fillRect(-enemy.width/2 + 5, enemy.height/2 - 5, enemy.width, 10);
// Car body glow
ctx.shadowBlur = 15;
ctx.shadowColor = enemy.color;
// Main body
ctx.fillStyle = enemy.color;
ctx.fillRect(-enemy.width/2, -enemy.height/2, enemy.width, enemy.height);
// Windshield
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(-enemy.width/2 + 5, -enemy.height/2 + 10, enemy.width - 10, 20);
// Taillights (glowing red)
ctx.shadowBlur = 20;
ctx.shadowColor = '#ff0000';
ctx.fillStyle = '#ff0000';
ctx.fillRect(-enemy.width/2 + 2, enemy.height/2 - 5, 12, 5);
ctx.fillRect(enemy.width/2 - 14, enemy.height/2 - 5, 12, 5);
// Headlights (facing down since they're coming at us)
ctx.shadowBlur = 30;
ctx.shadowColor = '#ffffaa';
ctx.fillStyle = '#ffffaa';
ctx.fillRect(-enemy.width/2 + 5, -enemy.height/2 + 2, 10, 5);
ctx.fillRect(enemy.width/2 - 15, -enemy.height/2 + 2, 10, 5);
ctx.restore();
}
// Draw player
ctx.save();
ctx.translate(player.x, player.y);
ctx.rotate(player.tilt);
// Shadow
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.fillRect(-25, 35, 50, 15);
if (imageLoaded && customCarImage) {
// Custom car image
ctx.drawImage(customCarImage, -25, -40, 50, 80);
} else {
// Default neon car
ctx.shadowBlur = 25;
ctx.shadowColor = '#00d9ff';
// Body
ctx.fillStyle = '#00d9ff';
ctx.fillRect(-25, -40, 50, 80);
// Stripe
ctx.fillStyle = '#fff';
ctx.fillRect(-5, -40, 10, 80);
// Windshield
ctx.fillStyle = '#0a0a1a';
ctx.fillRect(-20, -30, 40, 25);
// Headlights (strong beams)
ctx.shadowBlur = 40;
ctx.shadowColor = '#ffffff';
ctx.fillStyle = '#ffffff';
ctx.fillRect(-20, -42, 15, 8);
ctx.fillRect(5, -42, 15, 8);
// Headlight beams
ctx.globalAlpha = 0.3;
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.moveTo(-15, -42);
ctx.lineTo(-40, -200);
ctx.lineTo(-5, -200);
ctx.fill();
ctx.beginPath();
ctx.moveTo(15, -42);
ctx.lineTo(40, -200);
ctx.lineTo(5, -200);
ctx.fill();
ctx.globalAlpha = 1;
// Taillights
ctx.shadowBlur = 20;
ctx.shadowColor = '#ff006e';
ctx.fillStyle = '#ff006e';
ctx.fillRect(-20, 35, 15, 5);
ctx.fillRect(5, 35, 15, 5);
}
ctx.restore();
// Speed lines (when fast)
if (speed > 10) {
ctx.strokeStyle = 'rgba(255,255,255,0.1)';
ctx.lineWidth = 2;
for (let i = 0; i < 3; i++) {
let x = Math.random() * canvas.width;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x - (Math.random() - 0.5) * 50, canvas.height);
ctx.stroke();
}
}
}
function gameOver() {
gameRunning = false;
if (score > highScore) {
highScore = Math.floor(score);
localStorage.setItem('neonRacerHigh', highScore);
}
document.getElementById('finalScore').textContent = Math.floor(score);
document.getElementById('finalHighScore').textContent = highScore;
document.getElementById('gameOver').style.display = 'block';
}
function resetGame() {
document.getElementById('gameOver').style.display = 'none';
startEngineSound();
gameRunning = true;
resetVariables();
gameLoop();
}
function gameLoop() {
if (!gameRunning) return;
update();
draw();
requestAnimationFrame(gameLoop);
}
// Initial draw
draw();